/***
 * Copyright (c) 2021-2031 murenchao
 * fig is licensed under Mulan PubL v2.
 * You can use this software according to the terms and conditions of the Mulan PubL v2.
 * You may obtain a copy of Mulan PubL v2 at:
 *       http://license.coscl.org.cn/MulanPubL-2.0
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PubL v2 for more details.
 */
package cool.taomu.software.fig.asm.utils

import java.util.HashMap
import java.util.Map
import org.eclipse.xtend.lib.annotations.Accessors
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.Label
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes
import org.objectweb.asm.Type
import org.objectweb.asm.commons.GeneratorAdapter
import org.objectweb.asm.commons.Method

class FigGeneratorAdapter {
    static interface FigAsm {
        def void code(FigGeneratorAdapter arg0);
    }

    @Accessors
    static class Variable {
        int index;
        Type type;

        new(int index, Type type) {
            this.index = index;
            this.type = type;
        }
    }

    Map<String, Variable> fields = new HashMap<String, Variable>();
    Map<String, Label> labels = new HashMap<String, Label>();
    GeneratorAdapter ga = null;
    MethodVisitor mv = null;

    new(int access, Method method, String signature, Type[] exceptions, ClassVisitor classVisitor) {
        mv = classVisitor.visitMethod(access, method.getName(), method.getDescriptor(), signature, null);
        ga = new GeneratorAdapter(access, method, mv);
    // ga = new GeneratorAdapter(access, method, signature, exceptions, classVisitor);
    }

    def newLables(String key) {
        labels.put(key, new Label());
    }

    def visitTryCatchBlock(String start, String end, String catchBlock) {
        this.visitTryCatchBlock(start, end, catchBlock, null);
    }

    def visitTryCatchBlock(String start, String end, String catchBlock, Class<?> zlass) {
        var l0 = labels.get(start);
        var l1 = labels.get(end);
        var cb = labels.get(catchBlock)
        if (zlass === null) {
            mv.visitTryCatchBlock(l0, l1, cb, null);
        } else {
            mv.visitTryCatchBlock(l0, l1, cb, Type.getType(zlass).getInternalName());
        }
    }

    def aconst_null() {
        ga.visitInsn(Opcodes.ACONST_NULL);
    }

    def mark(String key) {
        ga.mark(labels.get(key));
    }

    def goTo(String key) {
        ga.goTo(labels.get(key));
    }

    def newInstance(String name, Class<?> type) {
        var classType = Type.getType(type);
        ga.newInstance(classType);
        ga.dup;
        ga.invokeConstructor(classType, Method.getMethod("void <init>()"));
        var l1 = ga.newLocal(classType);
        ga.storeLocal(l1, classType)
        fields.put(name, new Variable(l1, classType));
    }

    def newArray(int size, Class<?> type, FigAsm asm) {
        this.newArray(null, size, type, asm);
    }

    def newArray(String name, int size, Class<?> type, FigAsm asm) {
        var classType = Type.getType(type);
        ga.push(size);
        ga.newArray(classType);
        asm.code(this);
        var l1 = ga.newLocal(classType);
        if (name !== null) {
            ga.storeLocal(l1, classType);
            fields.put(name, new Variable(l1, classType));
        }
    }

    def store(String name) {
        this.store(name, Object);
    }

    def store(String name, Class<?> type) {
        var classType = Type.getType(type);
        var v = fields.getOrDefault(name, new Variable(ga.newLocal(classType), classType))
        ga.storeLocal(v.index, v.type);
        fields.put(name,v);
    }

    def ifnull(String key) {
        ga.ifNull(labels.get(key));
    }
    def ifnonnull(String key) {
        ga.ifNonNull(labels.get(key));
    }
    def ifacmpeq(String key){
       ga.ifCmp(Type.getType(Object),Opcodes.IF_ACMPEQ,labels.get(key));
    }

    def loadLocal(String name) {
        var v = fields.get(name);
        ga.loadLocal(v.index, v.type);
    }

    def athrow() {
        ga.throwException();
    }

    def invokeVirtual(Class<?> type, String method, boolean defaultPackage) {
        ga.invokeVirtual(Type.getType(type), Method.getMethod(method, defaultPackage));
    }

    def invokeVirtual(Class<?> type, String method) {
        this.invokeVirtual(type, method, false);
    }

    def invokeVirtual(Class<?> type, java.lang.reflect.Method method) {
        ga.invokeVirtual(Type.getType(type), Method.getMethod(method));
    }

    def invokeStatic(Class<?> type, String method, boolean defaultPackage) {
        ga.invokeStatic(Type.getType(type), Method.getMethod(method, defaultPackage));
    }

    def invokeStatic(Class<?> type, String method) {
        this.invokeStatic(type, method, false);
    }

    def dup() {
        ga.dup();
    }

    def push(Class<?> zlass) {

        ga.push(Type.getType(zlass));
    }

    def push(Type type) {
        ga.push(type);
    }

    def push(String value) {
        ga.push(value);
    }

    def push(int value) {
        ga.push(value);
    }

    def returnValue() {
        ga.returnValue();
    }

    def endMethod() {
        ga.endMethod();
    }

    def checkCast(Class<?> type) {
        ga.checkCast(Type.getType(type));
    }

    def visitInsn(int i) {
        ga.visitInsn(i);
    }

    def visitVarInsn(int opcode, int v) {
        ga.visitVarInsn(opcode, v);
    }

    def loadArg(int arg) {
        ga.loadArg(arg);
    }

}
